﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Mls.IO;

namespace Mls
{
    public struct ColumnDefinition
    {
        public const double InchesMultiplier = 7.3;
        public const int DefaultTableIndent = 10;

        public int Start;
        public int End;
        public int Width { get { return Math.Abs(End - Start); } }
        public double WidthInInches { get { return Width / InchesMultiplier; } }

        public Smartee.RichText.Justification Justify;
    }

    public abstract class AbstractParser
    {
        #region Commands Definition
        private enum Command
        {
            Unknown,
            NJ,
            LJ,
            RJ,
            CJ,
            BJ,
            BF,
            FN,
            HE,
            FT,
            TB,
            PA,
            PN,
            PC,//End of important commands
            BM,
            CS,
            LI,
            PI,
            LM,
            LS,
            NF,
            NS,
            NT,
            PS,
            RI,
            RM,
            SS,
            ST,
            TM,
            UO,
            UW
        };

        private static Dictionary<string, Command> _commands = new Dictionary<string, Command>();
        private static Dictionary<char, Smartee.RichText.Justification> _justifications = new Dictionary<char, Smartee.RichText.Justification>();

        static AbstractParser()
        {
            _commands.Add("NJ", Command.NJ);
            _commands.Add("LJ", Command.LJ);
            _commands.Add("RJ", Command.RJ);
            _commands.Add("CJ", Command.CJ);
            _commands.Add("BJ", Command.BJ);
            _commands.Add("BF", Command.BF);
            _commands.Add("FN", Command.FN);
            _commands.Add("HE", Command.HE);
            _commands.Add("FT", Command.FT);
            _commands.Add("TB", Command.TB);
            _commands.Add("PA", Command.PA);
            _commands.Add("PN", Command.PN);
            _commands.Add("PC", Command.PC);
            _commands.Add("BM", Command.BM);
            _commands.Add("CS", Command.CS);
            _commands.Add("LI", Command.LI);
            _commands.Add("PI", Command.PI);
            _commands.Add("LM", Command.LM);
            _commands.Add("LS", Command.LS);
            _commands.Add("NF", Command.NF);
            _commands.Add("NS", Command.NS);
            _commands.Add("NT", Command.NT);
            _commands.Add("PS", Command.PS);
            _commands.Add("RI", Command.RI);
            _commands.Add("RM", Command.RM);
            _commands.Add("SS", Command.SS);
            _commands.Add("ST", Command.ST);
            _commands.Add("TM", Command.TM);
            _commands.Add("UO", Command.UO);
            _commands.Add("UW", Command.UW);

            _justifications.Add('N', Smartee.RichText.Justification.Nutral);
            _justifications.Add('L', Smartee.RichText.Justification.Left);
            _justifications.Add('R', Smartee.RichText.Justification.Right);
            _justifications.Add('C', Smartee.RichText.Justification.Center);
            _justifications.Add('B', Smartee.RichText.Justification.Full);
        }
        #endregion

        private const byte DefaultPageNumberCharacter = (byte)'#';

        protected Document _document;

        // Parser Status Variables
        private byte _pageNumberCharacter = DefaultPageNumberCharacter;

        public AbstractParser()
        {
        }

        private List<Character> _currentRule = new List<Character>();

        private Character Read()
        {
            Character character = _document.Read;
            _currentRule.Add(character);
            return character;
        }

        private Character[] ConsumeRuleText()
        {
            Character[] text = _currentRule.ToArray();
            _currentRule.Clear();
            return text;
        }

        private Character ConsumeRuleCharacter()
        {
            if (_currentRule.Count != 1)
                throw new InvalidProgramException("Expected one character to be in rule text");
            Character character = _currentRule[0];
            _currentRule.Clear();
            return character;
        }

        public virtual void Parse(Document document)
        {
            _document = document;

            _document.SeekBegining();

            while (true)
            {
                Character ahead = _document.Ahead;

                if (ahead.Type == Token.EndOfFile)
                    break;
                else if (ahead.Type == Token.Semicolon)
                    ParseCommandBlock(false);
                else if (ahead.Type == Token.Delimiter)
                    ParseTableRow(false);
                else
                {
                    ParseParagraph(false, false);
                    if (_document.Ahead.Type == Token.NewLine)
                    {
                        Read();
                        ParagraphSeparator(ConsumeRuleCharacter());
                    }
                }
            }
        }

        protected abstract void UnexpectedCharacters(Character[] text, string errorMessage);

        #region Parse Tables
        protected abstract void StartNewCell(Character[] text);
        protected abstract void EndRow(Character[] text);
        protected abstract void ParagraphSeparator(Character text);

        private void ParseTableRow(bool isHeaderOrFooter)
        {
            if (Read().Type != Token.Delimiter)
                throw new InvalidProgramException("Table row must start with delimiter");

            while (_document.Ahead.Type != Token.EndOfFile && _document.Ahead.Type != Token.NewLine)
            {
                StartNewCell(ConsumeRuleText());

                ParseParagraph(true, false);

                if (Read().Type != Token.Delimiter)
                {
                    UnexpectedCharacters(ConsumeRuleText(), "Cells must end with delimiter");
                    return;
                }
            }
            if (_document.Ahead.Type == Token.NewLine)
                Read();
            EndRow(ConsumeRuleText());
        }
        #endregion

        #region Parse Paragraphs and Contents
        protected abstract void ParagraphCharacter(Character character);
        protected abstract void PageNumber(Character character);
        protected abstract void StartEmbed(Character character);
        protected abstract void EndEmbed(Character character);

        private void ParseParagraph(bool delimited, bool embeded)
        {
            string text = string.Empty;

            while (_document.Ahead.Type != Token.NewLine && _document.Ahead.Type != Token.EndOfFile &&
                (!embeded || _document.Ahead.Type != Token.Embed || _document.ReadAhead(1).Type == Token.Semicolon) &&
                (!delimited || _document.Ahead.Type != Token.Delimiter))
            {
                Character character = Read();

                if (character.Type == Token.Embed && _document.Ahead.Type == Token.Semicolon)
                {
                    StartEmbed(ConsumeRuleCharacter());

                    ParseCommandBlock(true);

                    if (_document.Ahead.Type != Token.Embed)
                        UnexpectedCharacters(ConsumeRuleText(), "Embed character should end embeded command block");
                    else
                    {
                        Read();
                        EndEmbed(ConsumeRuleCharacter());
                    }
                }
                else if (character.Type == Token.Character || character.Type == Token.HardSpace ||
                    character.Type == Token.ReverseIndent || character.Type == Token.ZeroSpace)
                    InterpretCharacter(character);
                else if (character.Type == Token.Semicolon)
                    UnexpectedCharacters(ConsumeRuleText(), "Semicolon found in text");
                else
                    UnexpectedCharacters(ConsumeRuleText(), "Character of unexpected type");
            }
        }

        private void InterpretCharacter(Character character)
        {
            if (character.Raw == _pageNumberCharacter)
                PageNumber(ConsumeRuleCharacter());
            else
                ParagraphCharacter(ConsumeRuleCharacter());
        }
        #endregion

        #region Command Parsers
        protected abstract void StartCommandBlock();
        protected abstract void JustifyCommand(Character[] commandText, Smartee.RichText.Justification justify);
        protected abstract void BoldCommand(Character[] commandText, bool isBold);
        protected abstract void FontCommand(Character[] commandText, Language.Type language, int fontId);
        protected abstract void HeaderCommand(Character[] commandText);
        protected abstract void FooterCommand(Character[] commandText);
        protected abstract void TableCommand(Character[] commandText, ColumnDefinition[] definition);
        protected abstract void PageAdvanceCommand(Character[] commandText);
        protected abstract void PageNumberCommand(Character[] commandText, int newPageNumber);
        protected abstract void PageNumberCodeCommand(Character[] commandText);
        protected abstract void FootnoteCommand(Character[] commandText);
        protected abstract void InvalidCommandArguments(Character[] commandText);
        protected abstract void NotHandledCommand(Character[] commandText);
        protected abstract void UnknownCommand(Character[] commandText);
        protected abstract void IntercommandsWhitespaces(Character[] whitespaces);

        private void ParseCommandBlock(bool embeded)
        {
            if (!embeded)
                StartCommandBlock();

            do
            {
                if (Read().Type != Token.Semicolon)
                {
                    SkipCommand();
                    UnexpectedCharacters(ConsumeRuleText(), "Command not starting with semicolon");
                    continue;
                }

                ParseCommand();

                SkipWhiteSpaces();
                if (_currentRule.Count > 0)
                    IntercommandsWhitespaces(ConsumeRuleText());

            }
            while (_document.Ahead.Type != Token.NewLine && _document.Ahead.Type != Token.EndOfFile &&
                (!embeded || _document.Ahead.Type != Token.Embed));

            if (_document.Ahead.Type == Token.NewLine)
            {
                Read();
                IntercommandsWhitespaces(ConsumeRuleText());
            }
        }

        private void ParseCommand()
        {
            Character char1 = Read();
            Character char2 = Read();

            string commandName = (CharactersMapper.Map(char1) + CharactersMapper.Map(char2)).ToUpper();
            Command command = Command.Unknown;
            if (_commands.ContainsKey(commandName))
                command = _commands[commandName];

            switch (command)
            {
                case Command.NJ:
                case Command.LJ:
                case Command.RJ:
                case Command.CJ:
                case Command.BJ:
                    JustifyCommand(ConsumeRuleText(), _justifications[commandName[0]]);
                    break;
                case Command.BF:
                    ParseBoldCommand();
                    break;
                case Command.FN:
                    ParseFontCommand();
                    break;
                case Command.HE:
                    ParseHeaderCommand();
                    break;
                case Command.FT:
                    ParseFooter();
                    break;
                case Command.TB:
                    ParseTableDefinition();
                    break;
                case Command.PA:
                    PageAdvanceCommand(ConsumeRuleText());
                    break;
                case Command.PN:
                    ParsePageNumberCommand();
                    break;
                case Command.PC:
                    ParsePageNumberCodeCommand();
                    break;
                case Command.NT:
                    ParseFootnote();
                    break;

                //End of important commands
                case Command.BM:
                case Command.CS:
                case Command.LI:
                case Command.PI:
                case Command.LM:
                case Command.LS:
                case Command.NF:
                case Command.NS:
                case Command.PS:
                case Command.RI:
                case Command.RM:
                case Command.SS:
                case Command.ST:
                case Command.TM:
                case Command.UO:
                case Command.UW:
                    SkipCommand();
                    NotHandledCommand(ConsumeRuleText());
                    break;
                case Command.Unknown:
                    SkipCommand();
                    UnknownCommand(ConsumeRuleText());
                    break;
            }
        }

        private void ParseBoldCommand()
        {
            SkipWhiteSpaces();

            int value;

            if (_document.Ahead.Type == Token.Semicolon)
                BoldCommand(ConsumeRuleText(), true);
            else if (!ParseInteger(out value) || value != 0)
            {
                SkipCommand();
                InvalidCommandArguments(ConsumeRuleText());
            }
            else
                BoldCommand(ConsumeRuleText(), false);
        }

        private void ParseFontCommand()
        {
            SkipWhiteSpaces();
            int languageId;
            if (!ParseInteger(out languageId))
            {
                SkipCommand();
                InvalidCommandArguments(ConsumeRuleText());
                return;
            }
            SkipWhiteSpaces();
            if (!CheckForComma())
            {
                SkipCommand();
                InvalidCommandArguments(ConsumeRuleText());
                return;
            }
            SkipWhiteSpaces();
            int fontId;
            if (!ParseInteger(out fontId))
            {
                SkipCommand();
                InvalidCommandArguments(ConsumeRuleText());
                return;
            }

            FontCommand(ConsumeRuleText(), _document.LanguageSettings.Resolve(languageId), fontId);
        }

        private void ParseHeaderCommand()
        {
            SkipWhiteSpaces();
            HeaderCommand(ConsumeRuleText());
            ParseTableRow(true);
        }

        private void ParseFooter()
        {
            SkipWhiteSpaces();
            FooterCommand(ConsumeRuleText());
            ParseTableRow(true);
        }

        private void ParseTableDefinition()
        {
            List<ColumnDefinition> definition = new List<ColumnDefinition>();

            do
            {
                SkipWhiteSpaces();
                int left;
                if (!ParseInteger(out left))
                {
                    SkipCommand();
                    InvalidCommandArguments(ConsumeRuleText());
                    return;
                }
                SkipWhiteSpaces();
                if (!CheckForComma())
                {
                    SkipCommand();
                    InvalidCommandArguments(ConsumeRuleText());
                    return;
                }
                SkipWhiteSpaces();
                int right;
                if (!ParseInteger(out right))
                {
                    SkipCommand();
                    InvalidCommandArguments(ConsumeRuleText());
                    return;
                }
                SkipWhiteSpaces();
                if (!CheckForComma())
                {
                    SkipCommand();
                    InvalidCommandArguments(ConsumeRuleText());
                    return;
                }
                SkipWhiteSpaces();
                char justChar = char.ToUpper((char)Read().Raw);
                if (!_justifications.ContainsKey(justChar))
                {
                    SkipCommand();
                    InvalidCommandArguments(ConsumeRuleText());
                    return;
                }
                Smartee.RichText.Justification justify = _justifications[justChar];
                SkipWhiteSpaces();

                definition.Add(new ColumnDefinition() { Start = right, End = left, Justify = justify });
            }
            while (CheckForComma());

            TableCommand(ConsumeRuleText(), definition.ToArray());
        }

        private void ParseFootnote()
        {
            FootnoteCommand(ConsumeRuleText());
            while (_document.Ahead.Type != Token.Embed || _document.ReadAhead(1).Type == Token.Semicolon)
            {
                if (_document.Ahead.Type == Token.EndOfFile)
                {
                    UnexpectedCharacters(ConsumeRuleText(), "Unexpected end of file while in footnote");
                    break;
                }

                if (_document.Ahead.Type == Token.Semicolon)
                    ParseCommandBlock(true);
                else
                {
                    ParseParagraph(false, true);

                    if (_document.Ahead.Type == Token.NewLine)
                    {
                        Read();
                        ParagraphSeparator(ConsumeRuleCharacter());
                    }
                }
            }
        }

        private void ParsePageNumberCodeCommand()
        {
            SkipWhiteSpaces();
            _pageNumberCharacter = Read().Raw;
            PageNumberCodeCommand(ConsumeRuleText());
        }

        private void ParsePageNumberCommand()
        {
            SkipWhiteSpaces();
            int newPageNumber;
            if (!ParseInteger(out newPageNumber))
                newPageNumber = 1;
            PageNumberCommand(ConsumeRuleText(), newPageNumber);
        }

        private void SkipCommand()
        {
            while (_document.Ahead.Type != Token.Semicolon && _document.Ahead.Type != Token.NewLine &&
                _document.Ahead.Type != Token.Delimiter && _document.Ahead.Type != Token.Embed &&
                _document.Ahead.Type != Token.EndOfFile)
                Read();
        }
        #endregion

        #region Utilities
        private void SkipWhiteSpaces()
        {
            while (_document.Ahead.Raw == 0x20 ||
                _document.Ahead.Raw == 0x9)
                Read();
        }

        private bool ParseInteger(out int value)
        {
            value = 0;

            string integer = string.Empty;
            while (char.IsDigit((char)_document.Ahead.Raw))
                integer += (char)Read().Raw;

            if (integer == string.Empty)
                return false;

            value = int.Parse(integer);
            return true;
        }

        private bool CheckForComma()
        {
            if (_document.Ahead.Raw == (byte)',')
            {
                Read();
                return true;
            }

            return false;
        }
        #endregion
    }
}
